
#include <string.h>
#include <curl/curl.h>
#include <string.h>
#include <map>
#include <thread>
#include <mutex>
#include "curl_ftp_upload.h"


/* 
    download: https://github.com/curl/curl/releases
    ref: https://curl.haxx.se/libcurl/c/ftpuploadresume.html
        https://curl.haxx.se/libcurl/c/ftpgetinfo.html
*/
static char s_ip[32] = { 0 };
static char s_user_key[128] = { 0 };
static std::map<std::thread::id, CURL*> s_curl_map;
static std::mutex s_lock;

/* parse headers for Content-Length */
static size_t getcontentlengthfunc(void *ptr, size_t size, size_t nmemb, void *stream)
{
    int r;
    long len = 0;

    r = sscanf((const char*)ptr, "Content-Length: %ld\n", &len);
    if (r)
        *((long *)stream) = len;

    return size * nmemb;
}

/* discard downloaded data */
static size_t discardfunc(void *ptr, size_t size, size_t nmemb, void *stream)
{
    (void)ptr;
    (void)stream;
    return size * nmemb;
}

/* read data to upload */
static size_t readfunc(void *ptr, size_t size, size_t nmemb, void *stream)
{
    FILE *f = (FILE*)stream;
    size_t n;

    if (ferror(f))
        return CURL_READFUNC_ABORT;

    n = fread(ptr, size, nmemb, f) * size;

    return n;
}


static int upload(CURL *curl, const char *remotepath, const char *localpath, double len, int timeout, int tries)
{
    FILE *f;
    double uploaded_len;
    CURLcode r = CURLE_GOT_NOTHING;
    int c;

    f = fopen(localpath, "rb");
    if (!f)
    {
        fprintf(stderr, "[upload] open %s failed!\n", localpath);
        return 1004;
    }

    curl_easy_setopt(curl, CURLOPT_UPLOAD, 1L);
    curl_easy_setopt(curl, CURLOPT_URL, remotepath);

    if (timeout)
        curl_easy_setopt(curl, CURLOPT_FTP_RESPONSE_TIMEOUT, timeout);

    curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, getcontentlengthfunc);
    curl_easy_setopt(curl, CURLOPT_HEADERDATA, &uploaded_len);

    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, discardfunc);

    curl_easy_setopt(curl, CURLOPT_READFUNCTION, readfunc);
    curl_easy_setopt(curl, CURLOPT_READDATA, f);

    /* disable passive mode */
    // curl_easy_setopt(curl, CURLOPT_FTPPORT, "-");
    // curl_easy_setopt(curl, CURLOPT_FTP_CREATE_MISSING_DIRS, 1L);

    uploaded_len = len;

    for (c = 0; (r != CURLE_OK) && (c < tries); c++)
    {
        /* are we resuming? */
        if (c || uploaded_len > 0)
        { /* yes */
          /* determine the length of the file already written */

          /*
           * With NOBODY and NOHEADER, libcurl will issue a SIZE
           * command, but the only way to retrieve the result is
           * to parse the returned Content-Length header. Thus,
           * getcontentlengthfunc(). We need discardfunc() above
           * because HEADER will dump the headers to stdout
           * without it.
           */
            curl_easy_setopt(curl, CURLOPT_NOBODY, 1L);
            curl_easy_setopt(curl, CURLOPT_HEADER, 1L);

            r = curl_easy_perform(curl);
            if (r != CURLE_OK)
                continue;

            curl_easy_setopt(curl, CURLOPT_NOBODY, 0L);
            curl_easy_setopt(curl, CURLOPT_HEADER, 0L);

#ifdef _WINDOWS
            _fseeki64(f, uploaded_len, SEEK_SET);
#else
            fseek(f, uploaded_len, SEEK_SET);
#endif

            curl_easy_setopt(curl, CURLOPT_APPEND, 1L);
        }
        else if (uploaded_len > 0)
        {
            char range[1024] = { 0 };
            sprintf(range, "%lf-", len);
            curl_easy_setopt(curl, CURLOPT_RANGE, range); 
#ifdef _WINDOWS
            _fseeki64(f, uploaded_len, SEEK_SET);
#else
            fseek(f, uploaded_len, SEEK_SET);
#endif
            curl_easy_setopt(curl, CURLOPT_APPEND, 1L);
        }
        else
        { /* no */
            curl_easy_setopt(curl, CURLOPT_APPEND, 0L);
        }

        r = curl_easy_perform(curl);
    }

    fclose(f);

    if (r == CURLE_OK)
    {
        return 0;
    }
    else 
    {
        fprintf(stderr, "[upload] %s\n", curl_easy_strerror(r));
        return 1005;
    }
}

void ROCK_API global_init(const char* ip, const char* username, const char* passwd)
{
	strcpy(s_ip, ip);
	sprintf(s_user_key, "%s:%s", username, passwd);
	curl_global_init(CURL_GLOBAL_DEFAULT);
	s_curl_map.clear();
}


int ROCK_API easy_init()
{
	CURL *curl;
	std::thread::id tid = std::this_thread::get_id();

    std::unique_lock<std::mutex> guard(s_lock);
    auto it = s_curl_map.find(tid);
    if (it == s_curl_map.end())
    {
        curl = curl_easy_init();
        s_curl_map.insert(std::pair<std::thread::id, CURL*>(tid, curl));
    }
    else
    {
        curl = it->second;
    }

	if (!curl)
	{
		fprintf(stderr, "curl_easy_init() failed");
		return 1001;
	}

	guard.unlock();
    return 0;
}

void ROCK_API easy_cleanup()
{
    std::unique_lock<std::mutex> guard(s_lock);
    std::thread::id tid = std::this_thread::get_id();
    CURL *curl = s_curl_map[tid];
    if (curl)
    {
        curl_easy_cleanup(curl);
        s_curl_map.erase(tid);
    }
}

void ROCK_API global_cleanup()
{
    curl_global_cleanup();
}

int ROCK_API upload_file2(const char *remotepath, const char *localpath, int timeout, int tries, int overlap)
{
    std::unique_lock<std::mutex> guard(s_lock);
    CURL *curl = s_curl_map[std::this_thread::get_id()];
    guard.unlock();

    if (!curl)
    {
        fprintf(stderr, "[upload_file2] cannot get curl instance!\n");
        return 1003;
    }

	char url[1024] = { 0 };
	sprintf(url, "ftp://%s/", s_ip);
	curl_easy_setopt(curl, CURLOPT_URL, url);
	curl_easy_setopt(curl, CURLOPT_USERPWD, s_user_key);

	printf("[easy_init] url-s_user_key: %s - %s\n", url, s_user_key);

	CURLcode res = curl_easy_perform(curl);
	/* Check for errors */
	if (res != CURLE_OK)
	{
		fprintf(stderr, "[easy_init] curl_easy_perform() failed: %s\n", curl_easy_strerror(res));
		return 1002;
	}
    long filetime = -1;
    double filesize = 0.0;
    char full_remotepath[1024] = { 0 };
    sprintf(full_remotepath, "ftp://%s/%s", s_ip, remotepath);
    const char *filename = strrchr(full_remotepath, '/') + 1;

    curl_easy_setopt(curl, CURLOPT_URL, full_remotepath);
    curl_easy_setopt(curl, CURLOPT_FTP_CREATE_MISSING_DIRS, 1L);
    curl_easy_setopt(curl, CURLOPT_USERPWD, s_user_key);
    /* No download if the file */
    curl_easy_setopt(curl, CURLOPT_NOBODY, 1L);
    curl_easy_setopt(curl, CURLOPT_HEADER, 1L);
    curl_easy_setopt(curl, CURLOPT_VERBOSE, 0L);


    res = curl_easy_perform(curl);
    if (CURLE_OK != res)
    {
        fprintf(stderr, "[upload_file2] curl_easy_perform() failed: %s\n", curl_easy_strerror(res));
        return 1004;
    }

    if (!overlap)
    {
        res = curl_easy_getinfo(curl, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &filesize);
        if ((CURLE_OK == res) && (filesize > 0.0))
            printf("filesize %s: %0.0f bytes\n", filename, filesize);
    }

	int ret = upload(curl, full_remotepath, localpath, filesize, timeout, tries);
	curl_easy_reset(curl);
	return ret;
}


int ROCK_API upload_file(const char *remotepath, const char *localpath)
{
    return upload_file2(remotepath, localpath, 0, 3, 0);
}
